package com.saascloud.idempotent.core.aspect;

import com.saascloud.aop.AopOrder;
import com.saascloud.core.exception.BizException;
import com.saascloud.core.exception.GlobalErrorCode;
import com.saascloud.idempotent.core.annotation.NoRepeatSubmit;
import com.saascloud.idempotent.core.expression.KeyResolver;
import com.saascloud.redis.core.TenantIgnore;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @Author: liangxu
 * @Contact: one_dream@aliyun.com
 * @Date: 2022/5/24 11:30
 * @Version: 1.0
 * @Description:
 */
@Aspect
@Slf4j
@Order(AopOrder.NOREPEAT_CHECK_ORDER)
public class NoRepeatSubmitAop {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Autowired
    private KeyResolver keyResolver;

    /**
     * 定义切入点
     */
    @Pointcut("@annotation(com.saascloud.idempotent.core.annotation.NoRepeatSubmit)")
    public void noRepeat() {
    }

    /**
     * 前置通知：在连接点之前执行的通知
     *
     * @param joinPoint
     */

    @Around("noRepeat()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        if (!method.isAnnotationPresent(NoRepeatSubmit.class)) {
            return joinPoint.proceed();
        }
        NoRepeatSubmit noRepeatSubmit = method.getAnnotation(NoRepeatSubmit.class);
        String key;

        // 若没有配置 幂等 标识编号，则使用 url + 参数列表作为区分
        if (!StringUtils.hasLength(noRepeatSubmit.key())) {
            key = getDefaultKey();
        } else {
            // 使用jstl 规则区分
            key = keyResolver.resolver(noRepeatSubmit, joinPoint);
        }

        // 获取注解
        long timeout = noRepeatSubmit.value();
        TenantIgnore.setIgnore(true);
        Boolean pass = null;
        try {
            pass = redisTemplate.opsForValue().setIfAbsent(key, key, timeout, TimeUnit.MILLISECONDS);
        } catch (Exception e) {
            log.error("幂等缓存服务异常", e);
            throw new BizException(GlobalErrorCode.INTERNAL_SERVER_ERROR);
        } finally {
            TenantIgnore.remove();
        }
        if (Boolean.FALSE.equals(pass)) {
            log.warn("重复请求{}--NoRepeatSubmitAop.before", key);
            throw new BizException(GlobalErrorCode.REQUEST_REPEAT_ERROR);
        } else {
            return joinPoint.proceed();
        }
    }

    /**
     * 获取默认的防重复key：ip+请求路径+请求参数
     *
     * @return
     */
    private String getDefaultKey() {
        // 接收到请求，记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        assert attributes != null;
        HttpServletRequest request = attributes.getRequest();
        // 此处可以用token或者JSessionId
        String ip = getIpAddr(request);
        //请求路径请求参数
        StringBuilder path = new StringBuilder(request.getServletPath());
        Map<String, String[]> maps = request.getParameterMap();
        for (Map.Entry<String, String[]> entry : maps.entrySet()) {
            path.append(entry.getKey()).append(":").append(Arrays.toString(entry.getValue())).append(";");
        }
        return ip + path;
    }

    public static String getIpAddr(HttpServletRequest request) {
        if (request == null) {
            return "unknown";
        }
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Forwarded-For");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Real-IP");
        }

        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }

        return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip;
    }
}

